#!/usr/bin/env python3



#==============================================================================
# Cmdline
#==============================================================================
import argparse

parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter, 
    description="""
TODO
""")
     
parser.add_argument('-v', '--verbose',
    action="store_true",
    help="verbose output" )
     
parser.add_argument('-j', '--json',
    action="store_true",
    help="stdin contains json entries (one per line), with the file content stored in 'data' encoded as base64" )
     
parser.add_argument('-e', '--examples-only',
    action="store_true",
    help="Only print the encoded examples" )

args = None




import requests
import json

# these work:
#
# curl -X POST -H "Content-Type:application/x-www-form-urlencoded" --data '{"bytes":{"type":"Buffer","data":[102,111,111]}}' "https://staging.gather.town/api/uploadImage"
#
# response = requests.post('https://staging.gather.town/api/uploadImage', headers={'Content-Type': 'application/json'}, data=b'{"bytes":{"type":"Buffer","data":[102,111,111]}}')

def data_to_upload_image_args(data):
  if isinstance(data, str):
    try:
      data = data.encode('utf8')
    except:
      data = data.encode('latin1')
  if isinstance(data, bytes):
    data = list(iter(data))
  data = {'spaceId': '', 'bytes': {'type': 'Buffer', 'data': data}}
  data = json.dumps(data)
  data = data.encode('utf8')
  return data

def report_error(caught, code=200, **kwds):
  message = str(caught)
  # sys.stderr.write('Failed for {} of data size {}: {}\n'.format(kwds, len(data), message))
  print(json_response(False, message, code=code, **kwds))
  sys.stdout.flush()

def on_err(thunk, *args, handler=report_error, exception=Exception, **kwds):
  try:
    return thunk(*args)
  except exception as e:
    return handler(e, **kwds)

def data_to_url(data, raise_on_error=True):
  data = data_to_upload_image_args(data)
  response = requests.post('https://staging.gather.town/api/uploadImage', 
      headers={'Content-Type': 'application/json'},
      data=data)
  if response.ok:
    return response.text
  if raise_on_error:
    response.raise_for_status()

def replace(s, a, b, *more):
  s = s.replace(a, b)
  if len(more) > 0:
    return replace(s, *more)
  return s

def path_id(path):
  return replace(path, ' ', '_', '_-_', '-', '/Users/bb/Downloads/celeste/Celeste_Atlases-Postdivided/Graphics/Atlases/', 'Celeste/', '/', '::').lower()

def json_response(ok, result, **kwds):
  if 'data' in kwds:
    kwds['size'] = len(kwds.pop('data'))
  if ok:
    if 'path' in kwds:
      path = kwds['path']
      if callable(path):
        path = path()
      key = ':id'
      fqn = path_id(path)
      kwds[':id'] = fqn
      stub = '&' if '?' in result else '?'
      stub += key + '=' + fqn
      result += stub
  kwds.update({'ok': ok, 'result': result})
  return json.dumps(kwds)

def data_to_url_thread(path):
  def thunk():
    data = filebytes(path)
    orig = data
    url = data_to_url(data)
    print(json_response(True, url, data=orig, path=path))
    sys.stdout.flush()
  thread = threading.Thread(target=lambda: on_err(thunk, path=path), daemon=True)
  thread.start()
  return thread

from concurrent.futures import ThreadPoolExecutor, as_completed
from functools import partial
from base64 import b64decode

def data_from_stdin():
  path='/dev/stdin'
  if not args.json:
    return data_to_url_thread('/dev/stdin').join()
  else:
    def thunk(line):
      #print(len(line))
      entry = json.loads(line)
      data = entry['data']
      del entry['data']
      data = b64decode(data)
      qs = '?' + '&'.join([':{key}={value}'.format(key=k, value=v) for k, v in entry.items()])
      orig = data
      url = data_to_url(data)
      print(json_response(True, url, data=orig, path=entry.get('path', path)))
      sys.stdout.flush()
    with ThreadPoolExecutor(max_workers=10) as e:
      def futures():
        for line in sys.stdin:
          yield e.submit(lambda line: on_err(partial(thunk, line=line), path=path), line)
      for results in as_completed(futures()):
        #print('result', results)
        pass

import httpx
import asyncio

sem = asyncio.BoundedSemaphore(64)

async def data_to_url_async(path):
  async with sem:
    data = filebytes(path)
    orig = data
    data = data_to_upload_image_args(data)
    async with httpx.AsyncClient() as client:
      try:
        response = await client.post('https://staging.gather.town/api/uploadImage',
            headers={'Content-Type': 'application/json'},
            data=data,
            timeout=httpx.Timeout(timeout=None))
        if response.status_code == 200:
          url = response.text
          print(json_response(True, url, data=orig, path=path))
          sys.stdout.flush()
        else:
          response.raise_for_status()
      except Exception as caught:
        report_error(caught, data=orig, path=path, code=response.status_code)

def preparg(path):
  if path == '-':
    path = '/dev/stdin'
  return path

def filebytes(path):
  if path == '/dev/stdin':
    return sys.stdin.buffer.read()
  with open(path, 'rb') as f:
    return f.read()

def has_stdin():
    return not sys.stdin.isatty()

import sys
import threading
import asyncio
import tqdm

def run():
  argv = args.args
  argv2 = []
  for arg in argv:
    if arg.startswith('@'):
      with open(arg[1:]) as f:
        argv2.extend(f.read().splitlines())
    else:
      argv2.append(arg)
  argv = argv2
  print(len(argv))
  thread = None
  if has_stdin() and len(argv) <= 0:
    data_from_stdin()
    # thread = data_to_url_thread('/dev/stdin')
    # # def thunk():
    # #   data = sys.stdin.buffer.read()
    # #   url = data_to_url(data)
    # #   print(json_response(True, url, data=data, path='/dev/stdin'))
    # # on_err(thunk, path='/dev/stdin')
    # ##
    # # try:
    # #   print(json_response(True, data_to_url(data), data=data, path='/dev/stdin'))
    # # except Exception as caught:
    # #   report_error(caught, data=data, path=path, code=response.status_code)
  loop = asyncio.get_event_loop()
  async_tasks = [loop.create_task(data_to_url_async(preparg(arg))) for arg in tqdm.tqdm(argv)]
  loop.run_until_complete(asyncio.gather(*async_tasks))
  if thread is not None:
    thread.join()
  # threads = [data_to_url_thread(arg) for arg in argv]
  # for thread in threads:
  #   thread.join()



def main():
    try:
        global args
        if not args:
            args, leftovers = parser.parse_known_args()
            args.args = leftovers
        return run()
    except IOError:
        # http://stackoverflow.com/questions/15793886/how-to-avoid-a-broken-pipe-error-when-printing-a-large-amount-of-formatted-data
        try:
            sys.stdout.close()
        except IOError:
            pass
        try:
            sys.stderr.close()
        except IOError:
            pass

if __name__ == "__main__":
    main()

